
package org.apache.solr.core;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.channels.FileChannel;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.XML;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SolrXMLSerializer {

    protected static Logger log = LoggerFactory.getLogger(SolrXMLSerializer.class);
    private final static String INDENT = "  ";

    /**
     * @param w Writer to use
     * @throws IOException If there is a low-level I/O error.
     */
    void persist(Writer w, SolrXMLDef solrXMLDef) throws IOException {

        w.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
        w.write("<solr");
        Map<String, String> rootSolrAttribs = solrXMLDef.solrAttribs;
        Set<String> solrAttribKeys = rootSolrAttribs.keySet();
        for(String key : solrAttribKeys) {
            String value = rootSolrAttribs.get(key);
            writeAttribute(w, key, value);
        }

        w.write(">\n");
        Properties containerProperties = solrXMLDef.containerProperties;
        if(containerProperties != null && !containerProperties.isEmpty()) {
            writeProperties(w, containerProperties, "  ");
        }
        w.write(INDENT + "<cores");
        Map<String, String> coresAttribs = solrXMLDef.coresAttribs;
        Set<String> coreAttribKeys = coresAttribs.keySet();
        for(String key : coreAttribKeys) {
            String value = coresAttribs.get(key);
            writeAttribute(w, key, value);
        }
        w.write(">\n");

        for(SolrCoreXMLDef coreDef : solrXMLDef.coresDefs) {
            persist(w, coreDef);
        }
        w.write(INDENT + "</cores>\n");
        w.write("</solr>\n");
    }

    /**
     * Writes the cores configuration node for a given core.
     */
    private void persist(Writer w, SolrCoreXMLDef coreDef) throws IOException {

        w.write(INDENT + INDENT + "<core");
        Set<String> keys = coreDef.coreAttribs.keySet();
        for(String key : keys) {
            writeAttribute(w, key, coreDef.coreAttribs.get(key));
        }
        Properties properties = coreDef.coreProperties;
        if(properties == null || properties.isEmpty()) {
            w.write("/>\n"); // core
        }
        else {
            w.write(">\n");
            writeProperties(w, properties, "      ");
            w.write(INDENT + INDENT + "</core>\n");
        }
    }

    private void writeProperties(Writer w, Properties props, String indent) throws IOException {

        for(Map.Entry<Object, Object> entry : props.entrySet()) {
            w.write(indent + "<property");
            writeAttribute(w, "name", entry.getKey());
            writeAttribute(w, "value", entry.getValue());
            w.write("/>\n");
        }
    }

    private void writeAttribute(Writer w, String name, Object value) throws IOException {

        if(value == null) {
            return;
        }
        w.write(" ");
        w.write(name);
        w.write("=\"");
        XML.escapeAttributeValue(value.toString(), w);
        w.write("\"");
    }

    void persistFile(File file, SolrXMLDef solrXMLDef) {

        log.info("Persisting cores config to " + file);

        File tmpFile = null;
        try {
            // write in temp first
            tmpFile = File.createTempFile("solr", ".xml", file.getParentFile());

            try(
                    FileOutputStream out = new FileOutputStream(tmpFile);
                    Writer writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));) {
                persist(writer, solrXMLDef);
            }
            // rename over origin or copy if this fails
            if(tmpFile != null) {
                if(tmpFile.renameTo(file)) {
                    tmpFile = null;
                }
                else {
                    fileCopy(tmpFile, file);
                }
            }
        }
        catch(FileNotFoundException xnf) {
            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, xnf);
        }
        catch(IOException xio) {
            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, xio);
        }
        finally {
            if(tmpFile != null) {
                if(!tmpFile.delete()) {
                    tmpFile.deleteOnExit();
                }
            }
        }
    }

    /**
     * Copies a src file to a dest file: used to circumvent the platform
     * discrepancies regarding renaming files.
     */
    private static void fileCopy(File src, File dest) throws IOException {

        IOException xforward = null;
        try(
            FileInputStream fis = new FileInputStream(src);
            FileOutputStream fos = new FileOutputStream(dest);
            FileChannel fcin = fis.getChannel();
            FileChannel fcout = fos.getChannel())
        {
            // do the file copy 32Mb at a time
            final int MB32 = 32 * 1024 * 1024;
            long size = fcin.size();
            long position = 0;
            while(position < size) {
                position += fcin.transferTo(position, MB32, fcout);
            }
        }
        catch(IOException xio) {
            xforward = xio;
        }

        if(xforward != null) {
            throw xforward;
        }
    }

    static public class SolrXMLDef {

        Properties containerProperties;
        Map<String, String> solrAttribs;
        Map<String, String> coresAttribs;
        List<SolrCoreXMLDef> coresDefs;
    }

    static public class SolrCoreXMLDef {

        Properties coreProperties;
        Map<String, String> coreAttribs;
    }
}